# -*- coding: utf-8 -*-
"""
海康工业相机实时显示程序
不需要界面，直接获取相机实时画面并用OpenCV显示
撕裂实时测量显示代码
"""

import sys
import cv2
import numpy as np
import threading
import time
import pygame
import os
import timeit
from CamOperation_class import CameraOperation, Is_mono_data, Is_color_data
from MvCameraControl_class import *
from MvErrorDefine_const import *
from CameraParams_header import *


# 音频播放线程类
class AudioAlertThread(threading.Thread):
    def __init__(self, audio_file="alert.wav"):
        super().__init__()
        self.audio_file = audio_file
        self.running = False
        self.should_play = False
        self.lock = threading.Lock()
        self.audio_initialized = False
        self.thread_started = False
        
        # 不在这里初始化音频，而是在线程启动时初始化
    
    def _initialize_audio(self):
        """初始化音频系统"""
        # 检查脚本所在目录
        script_dir = os.path.dirname(os.path.abspath(__file__))
        
        # 检查当前工作目录
        current_dir = os.getcwd()
        
        # 尝试多个可能的路径
        possible_paths = [
            self.audio_file,  # 当前目录
            os.path.join(script_dir, self.audio_file),  # 脚本目录
            os.path.join(current_dir, self.audio_file),  # 当前工作目录
        ]
        
        audio_file_path = None
        for path in possible_paths:
            if os.path.exists(path):
                audio_file_path = path
                break
        
        if audio_file_path is None:
            return False
        
        # 更新音频文件路径
        self.audio_file = audio_file_path
        
        # 初始化pygame
        pygame.init()
        
        # 初始化音频系统
        pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=1024)
        
        # 加载音频文件
        pygame.mixer.music.load(self.audio_file)
        
        # 设置音量
        pygame.mixer.music.set_volume(1.0)
        
        self.audio_initialized = True
        return True
    
    def start_alert(self):
        """开始播放警报"""
        if not self.audio_initialized:
            return
        
        with self.lock:
            self.should_play = True
    
    def stop_alert(self):
        """停止播放警报"""
        with self.lock:
            self.should_play = False
            pygame.mixer.music.stop()
    
    def stop(self):
        """停止线程"""
        self.running = False
        self.stop_alert()
    
    def run(self):
        """音频播放线程主函数"""
        # 设置线程状态
        self.running = True
        self.thread_started = True
        
        # 在线程中初始化音频
        if not self._initialize_audio():
            self.running = False
            return
        
        while self.running:
            with self.lock:
                should_play = self.should_play
            
            if should_play:
                if not pygame.mixer.music.get_busy():
                    pygame.mixer.music.play(-1)  # -1表示循环播放
            else:
                pygame.mixer.music.stop()
            
            time.sleep(0.1)  # 检查间隔


# 图像处理线程类
class ImageProcessingThread(threading.Thread):
    def __init__(self, camera_operation, audio_thread=None):
        super().__init__()
        self.camera_operation = camera_operation
        self.audio_thread = audio_thread  # 音频线程引用
        self.running = False
        self.last_frame_time = time.perf_counter()
        self.frame_count = 0
        self.fps = 0.0             # 处理FPS初始值
        self.camera_fps = 0.0      # 相机FPS初始值
        self.camera_frame_count = 0  # 相机帧计数
        self.camera_last_time = time.perf_counter()  # 相机帧时间
        self.roi_selected = False  # ROI是否已选择
        self.roi_coords = None  # ROI坐标
        self.first_frame = True  # 是否为第一帧
        self.kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
        self.last_contour_count = 0  # 上次检测到的轮廓数量
    
    def stop(self):
        """停止线程"""
        self.running = False
        
    def run(self):
        """Image processing thread main function"""
        self.running = True
        
        while self.running:
            # 检查是否有新图像数据
            if (self.camera_operation.buf_save_image is not None and 
                self.camera_operation.st_frame_info is not None):
                
                # 立即获取锁处理图像，确保每帧都被处理
                if self.camera_operation.buf_lock.acquire(blocking=True, timeout=0.1):
                    # 计算相机FPS
                    
                    self.camera_frame_count += 1
                    current_time = time.perf_counter()
                    if current_time - self.camera_last_time > 1.0:
                        self.camera_fps = self.camera_frame_count / (current_time - self.camera_last_time)
                        self.camera_frame_count = 0
                        self.camera_last_time = current_time
                    

                    # 立即处理图像数据
                    processed_image = self._process_image()
                    if processed_image is not None:
                        # 实时处理图像
                        # 使用更精准的时间测量
                        start_time = time.perf_counter()
                        processed_result = self._process_image_realtime(processed_image)
                        end_time = time.perf_counter()
                        process_time = (end_time - start_time) * 1000  # 转换为毫秒
                        
                        # 计算处理FPS
                        if process_time > 0:
                            process_fps = 1000.0 / process_time
                        else:
                            process_fps = 0.0
                        
                        # 在终端中打印FPS
                        print(f"处理FPS: {process_fps:.1f}")
                        
                        
                        if processed_result is not None:
                            original_image, roi = processed_result
                            
                            # 创建合并显示图像 - 使用原始图像作为第一个参数来提取ROI原图
                            display_image = self._create_combined_display(original_image, original_image, roi)
                            
                            # 立即显示处理结果
                            if display_image is not None:
                                cv2.imshow("Hikvision Camera - Real-time Image", display_image)
                                
                                # 处理按键
                                key = cv2.waitKey(1) & 0xFF
                                if key == ord('q'):
                                    print("User pressed 'q' key, exiting...")
                                    self.running = False
                                elif key == ord('s'):
                                    # 保存图像
                                    timestamp = time.strftime("%Y%m%d_%H%M%S")
                                    filename = f"hikvision_capture_{timestamp}.jpg"
                                    cv2.imwrite(filename, display_image)
                                elif key == ord('r'):
                                    # 重新选择ROI
                                    self.roi_selected = False
                                    self.roi_coords = None
                                    self.first_frame = True
                        
                        # 统计处理FPS - 在成功处理图像后计算
                        self._update_processing_fps()
                    
                    # 释放锁
                    self.camera_operation.buf_lock.release()
                else:
                    # 如果超时无法获取锁，跳过此帧
                    time.sleep(0.001)
            else:
                # 如果没有图像数据，短暂等待
                time.sleep(0.001)
    
    def _process_image(self):
        """处理图像数据"""
        # 将图像数据转换为numpy数组
        image_data = np.frombuffer(self.camera_operation.buf_save_image, dtype=np.uint8)
        
        # 根据像素格式重塑图像
        width = self.camera_operation.st_frame_info.nWidth
        height = self.camera_operation.st_frame_info.nHeight
        pixel_type = self.camera_operation.st_frame_info.enPixelType
        
        # 验证图像数据大小
        actual_size = len(image_data)
        expected_size = width * height
        
        # 根据像素格式计算期望大小
        if pixel_type == PixelType_Gvsp_BGR8_Packed:
            expected_size *= 3
        elif Is_color_data(pixel_type):
            expected_size *= 3
        elif pixel_type == PixelType_Gvsp_Mono10 or pixel_type == PixelType_Gvsp_Mono12:
            expected_size *= 2
        elif pixel_type == PixelType_Gvsp_Mono10_Packed or pixel_type == PixelType_Gvsp_Mono12_Packed:
            if pixel_type == PixelType_Gvsp_Mono10_Packed:
                expected_size = (width * height * 10 + 7) // 8
            elif pixel_type == PixelType_Gvsp_Mono12_Packed:
                expected_size = (width * height * 12 + 7) // 8
        
        # 检查数据大小是否合理
        tolerance = max(1, expected_size // 100)
        if abs(actual_size - expected_size) > tolerance:
            return None
        
        # 处理不同的像素格式
        image = None
        if Is_mono_data(pixel_type):
            if pixel_type == PixelType_Gvsp_Mono10 or pixel_type == PixelType_Gvsp_Mono12:
                if actual_size >= width * height * 2:
                    image = image_data[:width * height * 2].reshape((height, width, 2))
                    image = image.astype(np.uint16)
                    image = (image >> 2).astype(np.uint8)
                    image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
            else:
                if actual_size >= width * height:
                    image = image_data[:width * height].reshape((height, width))
                    image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
        elif Is_color_data(pixel_type):
            if actual_size >= width * height * 3:
                image = image_data[:width * height * 3].reshape((height, width, 3))
        else:
            if actual_size >= width * height:
                image = image_data[:width * height].reshape((height, width))
                if len(image.shape) == 2:
                    image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
        
        # 检查图像是否有效
        if image is None or image.size == 0:
            return None
        
        # 验证图像尺寸
        if image.shape[0] != height or image.shape[1] != width:
            return None
        
        return image
    
    def _process_image_realtime(self, image):
        """
        实时图像处理函数 - ROI区域大津法二值化
        Args:
            image: 输入图像 (numpy array)
        Returns:
            processed_image: 处理后的图像 (numpy array)
            roi: ROI处理后的图像
        """
        # 如果是第一帧，等待用户选择ROI
        
        if self.first_frame:
            self._select_roi_first_frame(image)
            self.first_frame = False
            # 选择完成后直接进入处理流程
            if self.roi_selected and self.roi_coords is not None:
                return self._process_roi_image(image)
            else:
                return image, None
        
        # 如果ROI未选择，显示提示
        if not self.roi_selected:
            self._draw_roi_selection_prompt(image)
            return image, None
        
        # ROI已选择，进行ROI处理
        result = self._process_roi_image(image)
        
        return result
    
    def _process_roi_image(self, image):
        """
        处理ROI图像
        Args:
            image: 输入图像
        Returns:
            processed_image: 处理后的图像
            processed_roi: ROI处理后的图像
        """
        # 确保ROI已选择
        if not self.roi_selected or self.roi_coords is None:
            return image, None
        
        # 获取ROI区域
        roi = self._get_roi_region(image)
        if roi is not None and roi.size > 0:
            # 对ROI区域使用大津法进行二值化
            binary_roi = self._otsu_threshold_binarization(roi)
            binary_roi = cv2.dilate(binary_roi, self.kernel, iterations=1)
            # 轮廓检测
            processed_roi = self._detect_contours_and_display(binary_roi)
            
            # 返回原始图像和处理后的ROI图像
            return image, processed_roi
        
        return image, None
    
    def _get_roi_region(self, image):
        """
        获取ROI区域
        Args:
            image: 输入图像
        Returns:
            roi: ROI区域图像，如果未设置则返回None
        """
        # 如果ROI未选择，返回None
        if not self.roi_selected or self.roi_coords is None:
            return None
        
        # 提取ROI区域
        x1, y1, x2, y2 = self.roi_coords
        
        # 确保坐标在图像范围内
        height, width = image.shape[:2]
        x1 = max(0, min(x1, width-1))
        y1 = max(0, min(y1, height-1))
        x2 = max(0, min(x2, width))
        y2 = max(0, min(y2, height))
        
        roi = image[y1:y2, x1:x2]
        
        return roi
    
    def _otsu_threshold_binarization(self, roi):
        """
        对ROI区域使用大津法进行二值化
        Args:
            roi: ROI区域图像
        Returns:
            binary_roi: 二值化后的ROI图像
        """
        # 转换为灰度图
        gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
        
        # 应用高斯模糊减少噪声
        blurred = cv2.GaussianBlur(gray_roi, (5, 5), 0)
        
        # 使用大津法进行二值化
        # 大津法自动计算最优阈值，适合提取中心光条
        _, binary_roi = cv2.threshold(
            blurred, 
            0,      # 阈值（大津法会自动计算，这里设为0）
            255,    # 最大值
            cv2.THRESH_BINARY + cv2.THRESH_OTSU  # 大津法 + 二值化
        )
        
        return binary_roi
    
    def _detect_contours_and_display(self, binary_roi):
        """
        对二值图像进行轮廓检测并显示
        Args:
            binary_roi: 二值化ROI图像
        Returns:
            contour_image: 显示轮廓的彩色图像
        """
        # 将二值图像转换为彩色图像，保持光条为白色
        contour_image = cv2.cvtColor(binary_roi, cv2.COLOR_GRAY2BGR)
        
        # 查找轮廓
        contours, hierarchy = cv2.findContours(binary_roi, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        
        # 定义不同颜色的轮廓
        colors = [
            (0, 255, 0),    # 绿色
            (255, 0, 0),    # 蓝色
            (0, 0, 255),    # 红色
            (255, 255, 0),  # 青色
            (255, 0, 255),  # 洋红色
            (0, 255, 255),  # 黄色
            (128, 0, 128),  # 紫色
            (255, 165, 0),  # 橙色
        ]
        
        # 为每个轮廓绘制不同颜色的轮廓线
        for i, contour in enumerate(contours):
            color = colors[i % len(colors)]  # 循环使用颜色
            cv2.drawContours(contour_image, [contour], -1, color, 1)  # 线宽从2减小到1
        
        # 如果检测到多个轮廓，标记最左和最右侧的像素
        if len(contours) > 1:
            self._mark_extreme_points(contour_image, contours)
        
        # 在图像上显示轮廓数量和FPS信息
        contour_count = len(contours)
        self._draw_contour_info(contour_image, contour_count)
        
        # 检查轮廓数量变化并控制音频
        self._check_contour_alert(contour_count)
        
        return contour_image
    
    def _check_contour_alert(self, contour_count):
        """
        检查轮廓数量并控制音频警报
        Args:
            contour_count: 当前检测到的轮廓数量
        """
        if self.audio_thread is None:
            return
        
        # 检查音频线程是否正在运行
        if not self.audio_thread.is_alive():
            return
        
        # 检查音频线程是否已初始化
        if not getattr(self.audio_thread, 'audio_initialized', False):
            return
        
        # 检查轮廓数量是否大于1（发生撕裂）
        if contour_count > 1:
            # 如果轮廓数量大于1且之前没有警报，开始播放
            if self.last_contour_count <= 1:
                self.audio_thread.start_alert()
        else:
            # 如果轮廓数量小于等于1且之前有警报，停止播放
            if self.last_contour_count > 1:
                self.audio_thread.stop_alert()
        
        # 更新上次轮廓数量
        self.last_contour_count = contour_count
    
    def _draw_contour_info(self, image, contour_count):
        """
        在图像上绘制轮廓数量和FPS信息
        Args:
            image: 输入图像
            contour_count: 轮廓数量
        """
        # 设置文本参数
        font = cv2.FONT_HERSHEY_SIMPLEX
        font_scale = 0.6
        thickness = 2
        color = (255, 255, 255)  # 白色文字
        
        # 获取图像尺寸
        height, width = image.shape[:2]
        
        # 绘制轮廓数量信息（左上角）
        contour_text = f"Contours: {contour_count}"
        (text_width, text_height), baseline = cv2.getTextSize(contour_text, font, font_scale, thickness)
        
        # 如果轮廓数量大于1，显示警报信息
        if contour_count > 1:
            alert_text = "ALERT!"
            alert_color = (0, 0, 255)  # 红色警报
            # 绘制警报背景
            cv2.rectangle(image, 
                         (10 - 5, 10 - text_height - 5),
                         (10 + text_width + 80, 10 + text_height + 20),
                         (0, 0, 0), -1)
            # 绘制轮廓数量文本
            cv2.putText(image, contour_text, (10, 10 + text_height), 
                       font, font_scale, color, thickness)
            # 绘制警报文本
            cv2.putText(image, alert_text, (10 + text_width + 10, 10 + text_height), 
                       font, font_scale, alert_color, thickness)
        else:
            # 绘制背景矩形
            cv2.rectangle(image, 
                         (10 - 5, 10 - text_height - 5),
                         (10 + text_width + 5, 10 + 5),
                         (0, 0, 0), -1)
            # 绘制轮廓数量文本
            cv2.putText(image, contour_text, (10, 10 + text_height), 
                       font, font_scale, color, thickness)
        

    
    def _mark_extreme_points(self, image, contours):
        """
        标记轮廓的最左和最右侧像素位置
        Args:
            image: 输入图像
            contours: 轮廓列表
        """
        for contour in contours:
            # 找到轮廓的最左和最右侧点
            leftmost_point = tuple(contour[contour[:, :, 0].argmin()][0])
            rightmost_point = tuple(contour[contour[:, :, 0].argmax()][0])
            
            # 用红色圆圈标记最左侧点
            cv2.circle(image, leftmost_point, 10, (255, 255, 255), 1) 
            
            # 用蓝色圆圈标记最右侧点
            cv2.circle(image, rightmost_point, 10, (255, 255, 255), 1)
    
    def _overlay_binary_result(self, image, binary_roi, original_roi):
        """
        将二值化结果叠加到原图像上
        Args:
            image: 原图像
            binary_roi: 二值化后的ROI图像
            original_roi: 原始ROI图像
        """
        if binary_roi is None:
            return
        
        x1, y1, x2, y2 = self.roi_coords
        
        # 将二值化结果转换为彩色图像用于叠加
        binary_colored = cv2.cvtColor(binary_roi, cv2.COLOR_GRAY2BGR)
        
        # 创建掩码，只显示ROI区域
        mask = np.zeros_like(image)
        mask[y1:y2, x1:x2] = binary_colored
        
        # 将二值化结果叠加到原图像上（使用透明度）
        alpha = 0.7  # 透明度
        image[y1:y2, x1:x2] = cv2.addWeighted(
            image[y1:y2, x1:x2], 
            1 - alpha, 
            binary_colored, 
            alpha, 
            0
        )
    
    def _draw_roi_info(self, image, roi):
        """
        在图像上绘制ROI信息
        Args:
            image: 图像
            roi: ROI区域
        """
        if roi is None:
            return
        
        x1, y1, x2, y2 = self.roi_coords
        
        # 绘制ROI边界框
        cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
        
        # 绘制ROI标签
        label = "ROI"
        font = cv2.FONT_HERSHEY_SIMPLEX
        font_scale = 0.6
        thickness = 2
        color = (0, 255, 0)  # 绿色
        
        # 获取文本大小
        (text_width, text_height), baseline = cv2.getTextSize(label, font, font_scale, thickness)
        
        # 在ROI框上方绘制标签
        label_x = x1
        label_y = max(y1 - 10, text_height + 5)
        
        # 绘制背景矩形
        cv2.rectangle(image, 
                     (label_x - 5, label_y - text_height - 5),
                     (label_x + text_width + 5, label_y + 5),
                     (0, 0, 0), -1)
        
        # 绘制文本
        cv2.putText(image, label, (label_x, label_y), 
                   font, font_scale, color, thickness)
        
        # 显示ROI尺寸信息
        roi_width = x2 - x1
        roi_height = y2 - y1
        size_info = f"ROI: {roi_width}x{roi_height}"
        
        # 在图像左下角显示ROI信息
        info_x = 10
        info_y = image.shape[0] - 30
        
        # 绘制背景矩形
        cv2.rectangle(image, 
                     (info_x - 5, info_y - text_height - 5),
                     (info_x + text_width + 5, info_y + 5),
                     (0, 0, 0), -1)
        
        # 绘制文本
        cv2.putText(image, size_info, (info_x, info_y), 
                   font, font_scale, color, thickness)
    
    def set_roi_region(self, x1, y1, x2, y2):
        """
        设置ROI区域
        Args:
            x1, y1: ROI左上角坐标
            x2, y2: ROI右下角坐标
        """
        self.roi_coords = (x1, y1, x2, y2)
        self.roi_defined = True
    
    def _select_roi_first_frame(self, image):
        """
        在第一帧时选择ROI区域
        Args:
            image: 输入图像
        """
        # 创建窗口并设置鼠标回调
        window_name = "Select ROI Region"
        cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
        cv2.setMouseCallback(window_name, self._mouse_callback)
        
        # 初始化选择状态
        self.selecting_roi = True
        self.roi_start_point = None
        self.roi_end_point = None
        self.current_image = image.copy()
        
        # 设置超时时间，避免无限循环
        start_time = time.time()
        timeout = 30  # 30秒超时
        
        while self.selecting_roi:
            # 检查超时
            if time.time() - start_time > timeout:
                self.selecting_roi = False
                break
            
            # 显示当前图像
            display_image = self.current_image.copy()
            
            # 绘制选择框
            if self.roi_start_point and self.roi_end_point:
                cv2.rectangle(display_image, self.roi_start_point, self.roi_end_point, (0, 255, 0), 2)
            
            # 显示提示信息
            cv2.putText(display_image, "Drag mouse to select ROI", (10, 30), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
            cv2.putText(display_image, "Release mouse to confirm", (10, 60), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
            cv2.putText(display_image, "Press 'r' to reset", (10, 90), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
            cv2.putText(display_image, "Press 'q' to quit", (10, 120), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
            
            cv2.imshow(window_name, display_image)
            
            # 处理按键
            key = cv2.waitKey(1) & 0xFF
            if key == ord('q'):
                self.selecting_roi = False
                self.running = False
                break
            elif key == ord('r'):
                # 重置选择
                self.roi_start_point = None
                self.roi_end_point = None
                self.current_image = image.copy()
            
            # 检查ROI是否已选择
            if self.roi_selected:
                break
        
        # 清理窗口
        cv2.destroyAllWindows()
    
    def _mouse_callback(self, event, x, y, flags, param):
        """
        鼠标回调函数
        """
        if event == cv2.EVENT_LBUTTONDOWN:
            # 开始选择
            self.roi_start_point = (x, y)
            self.roi_end_point = None
        elif event == cv2.EVENT_MOUSEMOVE and self.roi_start_point:
            # 更新选择框
            self.roi_end_point = (x, y)
        elif event == cv2.EVENT_LBUTTONUP:
            # 完成选择
            self.roi_end_point = (x, y)
            # 鼠标松开时自动完成ROI选择
            if self.roi_start_point and self.roi_end_point:
                x1, y1 = self.roi_start_point
                x2, y2 = self.roi_end_point
                
                # 确保坐标顺序正确
                x1, x2 = min(x1, x2), max(x1, x2)
                y1, y2 = min(y1, y2), max(y1, y2)
                
                # 确保最小ROI尺寸
                if abs(x2 - x1) < 10 or abs(y2 - y1) < 10:
                    return
                
                # 设置ROI坐标和状态
                self.roi_coords = (x1, y1, x2, y2)
                self.roi_selected = True
                self.selecting_roi = False
                
                # 立即关闭ROI选择窗口
                cv2.destroyAllWindows()
    
    def _create_combined_display(self, original_image, processed_image, roi):
        """
        创建显示图像：显示ROI原图和ROI处理图像
        Args:
            original_image: 原始图像
            processed_image: 处理后的图像
            roi: ROI处理后的图像
        Returns:
            display_image: 显示图像
        """
        # 如果ROI存在，显示ROI原图和ROI处理图像
        if roi is not None and roi.size > 0:
            # 获取ROI原图
            roi_original = self._get_roi_region(original_image)
            if roi_original is not None and roi_original.size > 0:
                # 确保两个图像尺寸相同
                roi_height, roi_width = roi.shape[:2]
                roi_original_resized = cv2.resize(roi_original, (roi_width, roi_height))
                
                # 上下合并：上方显示ROI原图，下方显示ROI处理图像
                display_image = np.vstack([roi_original_resized, roi])
            else:
                display_image = roi.copy()
        else:
            # 如果ROI不存在，显示处理后的图像
            display_image = processed_image.copy()
        
        return display_image
    
    def _draw_roi_selection_prompt(self, image):
        """
        绘制ROI选择提示
        """
        # 在图像上显示提示信息
        cv2.putText(image, "ROI not selected", (10, 30), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
        cv2.putText(image, "Press 'r' to reset ROI selection", (10, 60), 
                   cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
    
    def _create_roi_display_image(self, original_image, roi, binary_roi):
        """
        创建只显示ROI区域的二值化图像
        Args:
            original_image: 原始图像
            roi: ROI区域
            binary_roi: 二值化后的ROI图像
        Returns:
            display_image: 显示图像
        """
        # 直接使用二值化图像作为显示图像
        if binary_roi is not None:
            display_image = binary_roi.copy()
        else:
            # 如果没有二值化结果，使用灰度ROI
            display_image = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
        
        # 不显示ROI信息
        
        
        return display_image
    
    def _draw_roi_info_on_binary_display(self, display_image):
        """
        在二值图像上绘制ROI信息（白色文字）
        """
        # 显示ROI尺寸信息
        height, width = display_image.shape[:2]
        size_info = f"ROI: {width}x{height}"
        
        # 设置文本参数
        font = cv2.FONT_HERSHEY_SIMPLEX
        font_scale = 0.6
        thickness = 2
        color = 255  # 白色（二值图像中255为白色）
        
        # 获取文本大小
        (text_width, text_height), baseline = cv2.getTextSize(size_info, font, font_scale, thickness)
        
        # 在图像右上角显示ROI信息
        text_x = display_image.shape[1] - text_width - 10
        text_y = text_height + 10
        
        # 绘制文本（白色文字）
        cv2.putText(display_image, size_info, (text_x, text_y), 
                   font, font_scale, color, thickness)
    
    def _draw_roi_info_on_display(self, display_image):
        """
        在显示图像上绘制ROI信息
        """
        # 显示ROI尺寸信息
        height, width = display_image.shape[:2]
        size_info = f"ROI: {width}x{height}"
        
        # 设置文本参数
        font = cv2.FONT_HERSHEY_SIMPLEX
        font_scale = 0.6
        thickness = 2
        color = (0, 255, 0)  # 绿色
        
        # 获取文本大小
        (text_width, text_height), baseline = cv2.getTextSize(size_info, font, font_scale, thickness)
        
        # 在图像右上角显示ROI信息
        text_x = display_image.shape[1] - text_width - 10
        text_y = text_height + 10
        
        # 绘制背景矩形
        cv2.rectangle(display_image, 
                     (text_x - 5, text_y - text_height - 5),
                     (text_x + text_width + 5, text_y + 5),
                     (0, 0, 0), -1)
        
        # 绘制文本
        cv2.putText(display_image, size_info, (text_x, text_y), 
                   font, font_scale, color, thickness)
    
    def reset_roi_to_center(self, image):
        """
        重置ROI到图像中心
        Args:
            image: 输入图像
        """
        height, width = image.shape[:2]
        roi_size = min(width, height) // 3
        
        x1 = (width - roi_size) // 2
        y1 = (height - roi_size) // 2
        x2 = x1 + roi_size
        y2 = y1 + roi_size
        
        self.set_roi_region(x1, y1, x2, y2)
    

    
    def _update_processing_fps(self):
        """更新处理FPS统计"""
        # 计算处理FPS - 统计1秒内处理的帧数
        self.frame_count += 1
        current_time = time.perf_counter()
        
        # 如果距离上次重置超过1秒，重新计算FPS
        if current_time - self.last_frame_time >= 1.0:
            # 计算1秒内处理的帧数
            self.fps = self.frame_count
            # 重置计数器
            self.frame_count = 0
            self.last_frame_time = current_time
    



def main():
    """Main function"""
    
    # Initialize SDK
    MvCamera.MV_CC_Initialize()
    # Enumerate devices
    deviceList = MV_CC_DEVICE_INFO_LIST()
    n_layer_type = (MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE
                    | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE)
    ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList)
    
    if ret != 0:
        print(f"Device enumeration failed! ret = {hex(ret)}")
        return

    if deviceList.nDeviceNum == 0:
        print("No devices found")
        return
    
    # Select first device
    nSelCamIndex = 0
    
    # Create camera object
    cam = MvCamera()
    obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex)
    
    # Open device
    ret = obj_cam_operation.Open_device()
    if ret != 0:
        print(f"Failed to open device! ret = {hex(ret)}")
        return
    
    # Set continuous mode
    ret = obj_cam_operation.Set_trigger_mode(False)
    if ret != 0:
        print(f"Failed to set continuous mode! ret = {hex(ret)}")
        return
    
    # Start grabbing
    ret = obj_cam_operation.Start_grabbing(0)  # Use 0 as window ID
    if ret != 0:
        print(f"Failed to start grabbing! ret = {hex(ret)}")
        return
    
    # Start audio alert thread
    audio_thread = AudioAlertThread("alert.wav")
    audio_thread.start()
    
    # 等待音频初始化完成
    time.sleep(1.0)
    init_wait_count = 0
    while not audio_thread.audio_initialized and init_wait_count < 50:
        time.sleep(0.1)
        init_wait_count += 1
    
    # Start image processing thread
    image_thread = ImageProcessingThread(obj_cam_operation, audio_thread)
    image_thread.start()
    
    print("Program running... Press 'q' to exit, 's' to save image, 'r' to reset ROI")
    
    # Wait for thread to end
    while image_thread.is_alive():
        # 检查线程是否应该停止
        if not image_thread.running:
            break
        time.sleep(0.1)
    
    # Clean up resources
    if 'image_thread' in locals():
        image_thread.stop()
        image_thread.join()
    
    if 'audio_thread' in locals():
        audio_thread.stop()
        audio_thread.join()
    
    if 'obj_cam_operation' in locals():
        obj_cam_operation.Stop_grabbing()
        obj_cam_operation.Close_device()
    
    # 关闭所有OpenCV窗口
    cv2.destroyAllWindows()
    
    # Finalize SDK
    MvCamera.MV_CC_Finalize()


if __name__ == "__main__":
    main()
